#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

#ifdef THINK_C
   typedef int bool;
#else
#  ifdef TRUE
#    ifndef bool
#      define bool int
#    endif
#  else
#    ifdef bool
#      define FALSE 0
#      define TRUE 1
#    else
       typedef enum {FALSE, TRUE} bool;
#    endif
#  endif
#endif

#define string char *
#define MAX_THREADS 3
#define MAX_HTML_CHARS 100000
#define MAX_EXTENSION_LENGTH 10
#define ON 1
#define OFF 0
#define MAX_COMMAND 20

typedef struct SocketData {
	SOCKET clientSocket;
	SOCKET serverSocket;
	SOCKET telnetSocket;
	struct sockaddr clientAddr;
	struct sockaddr telnetClientAddr;
	int telnetClientAddrLen;
	int clientAddrLen;
	string websitePath;
	string logPath;
	string telnetPass;
	string HTTPbuffer;
} SOCKETDATA, *PSOCKETDATA;

void writeServerLog(string ip, string recieved, string response, int sizeOfResponse, string serverLogPath);
void handleHTTP(SOCKET clientSocket, string command, string path, string protocol, string hostName, string serverLogPath, string HTTPbuffer);
DWORD WINAPI handleTelnet(LPVOID socketData);
string readFile(FILE *infile);
string readLine(FILE *infile);
string stringConcat(string s1, string s2);
bool fileExtension(string extension, string fileName);
DWORD WINAPI manageConnections(LPVOID socketData);
string createHeader(string path, string type, int errorCheck, string errorLine);
string getMonthName(int month);
string getDayName(int day);
string getLastModified(string path);
int getFileSize(string filename); 
DWORD WINAPI createHTTPThread(LPVOID socketData);
string checkForConditionalGet(string inputHTTP);
//string getLastModifiedForCompare(string filename);
string SubString(string s, int p1, int p2);

static int serverStatus = ON;

main(){
	WORD wVersionRequested;
	WSADATA wsaData;
	int err, status;
	struct addrinfo *infoHTTP;
	struct addrinfo *infoTelnet;
	PSOCKETDATA socketData = (PSOCKETDATA) malloc(sizeof(PSOCKETDATA));
	HANDLE hThreadArray[2];
	FILE *configFile;
	string portHTTP; 
	string portTelnet;

	configFile = fopen("config.txt", "r");
	if(configFile == NULL){
		portHTTP = "8080";
		portTelnet = "8081";
		socketData->websitePath = "website";
		socketData->telnetPass = "admin";
		socketData->logPath = "log/serverlog.txt";
	}
	else {
		portHTTP = readLine(configFile);
		portTelnet = readLine(configFile);
		socketData->websitePath = readLine(configFile);
		socketData->telnetPass = readLine(configFile);
		socketData->logPath = readLine(configFile);
	}
	fclose(configFile);

	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	
	//Creates a socket with 3 pre-defined values found in the libraries.
	socketData->serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	socketData->telnetSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	status = getaddrinfo("127.0.0.1", portHTTP, NULL, &infoHTTP);
	status = getaddrinfo("127.0.0.1", portTelnet, NULL, &infoTelnet);
	if(status != 0) {
		WCHAR * error = gai_strerror(status);
		printf("%s\n",error);
	}

	//Binds the port to the socket.
	status = bind(socketData->serverSocket, infoHTTP->ai_addr, infoHTTP->ai_addrlen); 
	status = bind(socketData->telnetSocket, infoTelnet->ai_addr, infoTelnet->ai_addrlen); 
	if(status == SOCKET_ERROR) {
		err = WSAGetLastError();
		printf("%d\n",err);
	}
	status = listen(socketData->serverSocket, SOMAXCONN);
	status = listen(socketData->telnetSocket, SOMAXCONN);
	if(status == SOCKET_ERROR) {
		err = WSAGetLastError();
		printf("%d\n",err);
	}

	socketData->clientAddrLen = sizeof(socketData->clientAddr);
	socketData->telnetClientAddrLen = sizeof(socketData->telnetClientAddr);

	//Threads
	hThreadArray[0] = CreateThread(NULL, 0, createHTTPThread, socketData, 0, NULL);	
	hThreadArray[1] = CreateThread(NULL, 0, handleTelnet, socketData, 0, NULL);	

	WaitForMultipleObjects(2, hThreadArray, TRUE, INFINITE);
	closesocket(socketData->serverSocket);
	WSACleanup();
}

DWORD WINAPI createHTTPThread(LPVOID input){
	PSOCKETDATA socketData = (PSOCKETDATA) input;
	SOCKETDATA tempData[100];
	int i = 0;

	while(TRUE){
		if(serverStatus == ON){
			printf("Waiting for HTTP connection...\n");
			socketData->clientSocket = accept(socketData->serverSocket, &socketData->clientAddr, &socketData->clientAddrLen);
				tempData[i].clientSocket = socketData->clientSocket;
				tempData[i].serverSocket = socketData->serverSocket;
				tempData[i].telnetSocket = socketData->telnetSocket;
				tempData[i].clientAddr = socketData->clientAddr;
				tempData[i].telnetClientAddr = socketData->telnetClientAddr;
				tempData[i].telnetClientAddrLen = socketData->telnetClientAddrLen;
				tempData[i].clientAddrLen = socketData->clientAddrLen;
				tempData[i].websitePath = socketData->websitePath;
				tempData[i].logPath = socketData->logPath;
				tempData[i].telnetPass = socketData->telnetPass;
				tempData[i].HTTPbuffer = socketData->HTTPbuffer;
			CreateThread(NULL, 0, manageConnections, &tempData, 0, NULL);
			i++;
			if(i == 99) i = 0;
		}
	}
}

DWORD WINAPI manageConnections(LPVOID input){
	int status, iResult, i = 0;
	char hostName[100], portName[100], buffer[1024], command[5], protocol[15];
	string path = (string) malloc(sizeof(char)*50);
	PSOCKETDATA socketData = (PSOCKETDATA) input;

	if(socketData->clientSocket != INVALID_SOCKET) {
		status = getnameinfo(&socketData->clientAddr, socketData->clientAddrLen, hostName, 100, portName, 100, NI_NUMERICSERV); 
		if(status == 0) {
			printf("\nConnected to: %s at port %s\n", hostName, portName);

			//Recieves data and stuffs it in buffer.
			iResult = recv(socketData->clientSocket, buffer, 1024, 0);
			
			//Prints the recieved message.
			fwrite(buffer, 1, iResult, stdout);

			//Put first line in variables.
			sscanf(buffer, "%s %s %s", command, path, protocol);

			//Changes to correct directory.
			if(strcmp(path, "/") == 0)
				path = stringConcat(socketData->websitePath, "/index.html");
			else
				path = stringConcat(socketData->websitePath, path);

			handleHTTP(socketData->clientSocket, command, path, protocol, hostName, socketData->logPath, buffer);
        }
		else
			printf("Error occured.");
	}
	closesocket(socketData->clientSocket);
	return 0;
}

DWORD WINAPI handleTelnet(LPVOID input){
	FILE *serverLog;
	string welcomeMsg = "Welcome to Dorbell Telnet Server\r\n";
	char message[MAX_COMMAND];
	char buffer;
	string sBuffer = (string) malloc(sizeof(char)*512);
	string command = (string) malloc(sizeof(char)*512);
	string argument = (string) malloc(sizeof(char)*512);
	int iResult, i, passCheck = 0;
	SOCKET telnetClientSocket;
	bool access = FALSE;
	PSOCKETDATA socketData = (PSOCKETDATA) input;

	telnetClientSocket = accept(socketData->telnetSocket, &socketData->telnetClientAddr, &socketData->telnetClientAddrLen);
	send(telnetClientSocket, welcomeMsg, strlen(welcomeMsg), 0);

	while(TRUE){
		send(telnetClientSocket, "\r\nINPUT>", strlen("\r\nINPUT>"), 0);
		for(i = 0; i < MAX_COMMAND; i++){
			iResult = recv(telnetClientSocket, &buffer, 1, 0);
			if(buffer == 13 || buffer == 3 ){
				message[i] = '\0';
				break;
			}
			printf("%c", buffer);
			message[i] = buffer;
		}

		sscanf(message, "%s %s", command, argument);

		if(strcmp(command, "PASS") == 0){
			if(strcmp(argument, socketData->telnetPass) == 0){
				send(telnetClientSocket, "\r\nYou are logged in.\r\n", strlen("\r\nYou are logged in.\r\n"), 0);
				passCheck = 1;
			}
			else
				send(telnetClientSocket, "\r\nIncorrect password.\r\n", strlen("\r\nIncorrect password.\r\n"), 0);
		}
		else if(strcmp(command, "WEBB") == 0 && passCheck == 1){
			if(strcmp(argument, "0") == 0){
				send(telnetClientSocket, "\r\nServer offline.\r\n", strlen("\r\nServer offline.\r\n"), 0);
				serverStatus = OFF;
			}
			else if(strcmp(argument, "1") == 0){
				send(telnetClientSocket, "\r\nServer online.\r\n", strlen("\r\nServer online.\r\n"), 0);
				serverStatus = ON;
			}
			else
				send(telnetClientSocket, "\r\nIncorrect argument.\r\n", strlen("\r\nIncorrect argument.\r\n"), 0);
		}
		else if(strcmp(command, "STATUS") == 0 && passCheck == 1){
			if(serverStatus == ON)
				send(telnetClientSocket, "\r\nServer is online\r\n", strlen("\r\nServer is online\r\n"), 0);
			else if(serverStatus == OFF)
				send(telnetClientSocket, "\r\nServer is offline\r\n", strlen("\r\nServer is offline\r\n"), 0);
		}
		else if(strcmp(command, "LOGG") == 0 && passCheck == 1){
			serverLog = fopen(socketData->logPath, "r");
			while(!feof(serverLog)){
				fread(sBuffer, 1, 512, serverLog);
				send(telnetClientSocket, sBuffer, 512, 0);
			}
			fclose(serverLog);
		}
		else{
			send(telnetClientSocket, "\r\nIncorrect command or not logged in.\r\n", strlen("\r\nIncorrect command or not logged in.\r\n"), 0);
		}
	}
}

int getFileSize(string filename) {
	struct stat st;
	stat(filename, &st);
	return st.st_size;
}

void writeServerLog(string ip, string recievedCMD, string recievedPath, string recievedProtocol, string response, int sizeOfResponse, string serverLogPath){
	FILE *log;
	SYSTEMTIME st;

	GetSystemTime(&st);
	log = fopen(serverLogPath, "a");
	fprintf(log, "IP: %s DaTi: %d-%d-%d %d:%d:%d Recv: %s %s %s Resp: %s Size: %d\n", ip, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, recievedCMD, recievedPath, recievedProtocol, response, sizeOfResponse);
	fclose(log);
}

void handleHTTP(SOCKET clientSocket, string command, string path, string protocol, string hostName, string serverLogPath, string HTTPbuffer){
	FILE *sendFile = fopen(path, "rb");
	int errorCheck = 400, fileTime = 1000;
	char sbuffer[512];
	string HTTPLastModified = (string) malloc(sizeof(char)*512);
	string header = createHeader(NULL, NULL, errorCheck, "Bad Request");

	HTTPLastModified = checkForConditionalGet(HTTPbuffer);

	if(strcmp(command, "GET") == 0){
		if(sendFile != NULL){
			errorCheck = 200;
			if(strcmp(HTTPLastModified, "No modify") != 0){ //Conditional GET.
				if(strcmp(getLastModified(path), HTTPLastModified) == 0){
					errorCheck = 304;
					header = createHeader(NULL, NULL, errorCheck, "Not Modified");
					getc(sendFile); //Compiler gets sad if file is opened without being used.
				}
			}
			else if(fileExtension("html", path) || fileExtension("htm", path)) { //.html-files.
				header = createHeader(path, "text/html", errorCheck, "OK");
			}
			else if(fileExtension("jpeg", path) || fileExtension("jpg", path)) { //.jpeg-files.
				header = createHeader(path, "image/jpeg", errorCheck, "OK");
			}
			else if(fileExtension("gif", path)) { //.gif-files.
				header = createHeader(path, "image/gif", errorCheck, "OK");
			}
			else if(fileExtension("png", path)) { //.png-files.
				header = createHeader(path, "image/png", errorCheck, "OK");
			}
			else if(fileExtension("zip", path)) { //.zip-files.
				header = createHeader(path, "application/zip", errorCheck, "OK");
			}
			else if(fileExtension("ico", path)) { //.ico-files.
				header = createHeader(path, "image/icon", errorCheck, "OK");
			}
			else { //Everything else
				header = createHeader(path, "*/*", errorCheck, "OK");
			}
		} else {
			errorCheck = 404;
			header = createHeader(NULL, NULL, errorCheck, "Not Found");
		}
	}
	//Sends header.
	send(clientSocket, header, strlen(header), 0);

	//Streams file in 512 large bits.
	if(errorCheck == 200){
		while(!feof(sendFile)){
			fread(sbuffer, 1, 512, sendFile);
			send(clientSocket, sbuffer, 512, 0);
		}
	}
	fclose(sendFile);
	printf("\n\nHeader sent:\n\n%s\n", header);

	//Writes a log for the server.
	if(errorCheck == 200)
		writeServerLog(hostName, command, path, protocol, path, (strlen(header)+getFileSize(path)), serverLogPath);
	else if(errorCheck == 404)
		writeServerLog(hostName, command, path, protocol, "404 Not Found", strlen(header), serverLogPath);
	else if(errorCheck == 400)
		writeServerLog(hostName, command, path, protocol, "400 Bad Request", strlen(header), serverLogPath);
	else if(errorCheck == 304)
		writeServerLog(hostName, command, path, protocol, "304 Not modified", strlen(header), serverLogPath);
}

string createHeader(string path, string type, int errorCheck, string errorLine){
	SYSTEMTIME st;
	string header = (string) malloc(sizeof(char)*1000);
	GetSystemTime(&st);

	if(errorCheck == 200){
		sprintf(header, "HTTP/1.1 %d %s\nConnection: close\nDate: %s, %d %s %d %d:%d:%d GMT\nServer: Dorbell/1.0 (Windows)\nLast-Modified: %s\nContent-Length: %d\nContent-Type: %s\n\n", 
			errorCheck,
			errorLine,
			getDayName(st.wDayOfWeek), 
			st.wDay, 
			getMonthName(st.wMonth), 
			st.wYear, 
			st.wHour, 
			st.wMinute, 
			st.wSecond, 
			getLastModified(path), 
			getFileSize(path), 
			type);
	} else {
		sprintf(header, "HTTP/1.1 %d %s\nConnection: close\nDate: %s, %d %s %d %d:%d:%d GMT\nServer: Dorbell/1.0 (Windows)\n\n",
			errorCheck,
			errorLine,
			getDayName(st.wDayOfWeek), 
			st.wDay, 
			getMonthName(st.wMonth), 
			st.wYear, 
			st.wHour, 
			st.wMinute, 
			st.wSecond);
	}

	return header;
}

string getLastModified(string filename){
	string timeStr = (string) malloc(sizeof(char)*100);
	struct stat buffer;

	stat(filename, &buffer);

	strftime(timeStr, 100, "%Y-%m-%d", localtime( &buffer.st_mtime));
	return timeStr; //"1989-01-19";
}

string getDayName(int day){
	switch(day){
		case 0:
			return "Sun";
		case 1:
			return "Mon";
		case 2:
			return "Tue";
		case 3:
			return "Wed";
		case 4:
			return "Thu";
		case 5:
			return "Fri";
		case 6:
			return "Sat";
		default:
			return "Unknown";
	}
}

string getMonthName(int month){
	switch(month){
		case 1:
			return "Jan";
		case 2:
			return "Feb";
		case 3:
			return "Mar";
		case 4:
			return "Apr";
		case 5:
			return "May";
		case 6:
			return "Jun";
		case 7:
			return "Jul";
		case 8:
			return "Aug";
		case 9:
			return "Sep";
		case 10:
			return "Oct";
		case 11:
			return "Nov";
		case 12:
			return "Dec";
		default:
			return "Unknown";
	}
}

string readFile(FILE *infile){
	string line;
	string nline;
	int n = 0, ch, size = MAX_HTML_CHARS;

	line = (string) malloc(size + 1);
	while ((ch = getc(infile)) != EOF) {
		line[n++] = ch;
	}
	if (n == 0 && ch == EOF) {
		free(line);
		return (NULL);
	}
	line[n] = '\0';
	nline = (string) malloc(n + 1);
	strcpy(nline, line);
	free(line);
	return(nline);
}

string readLine(FILE *infile){
	string line;
	string nline;
	int n = 0, ch, size = MAX_HTML_CHARS;

	line = (string) malloc(size + 1);
	while ((ch = getc(infile)) != '\n' && ch != EOF) {
		line[n++] = ch;
	}
	if (n == 0 && ch == EOF) {
		free(line);
		return (NULL);
	}
	line[n] = '\0';
	nline = (string) malloc(n + 1);
	strcpy(nline, line);
	free(line);
	return(nline);
}

string stringConcat(string s1, string s2){
	string s;
	int len1, len2;

	len1 = strlen(s1);
	len2 = strlen(s2);
	s = (string) malloc(len1 + len2 + 1);
	strcpy(s, s1);
	strcpy(s + len1, s2);
	return (s);
}

bool fileExtension(string extension, string fileName) {
	int i = 0, j = 0;
	string fileExtension = (string) malloc(sizeof(char)*MAX_EXTENSION_LENGTH);
	while(fileName[i++] != '.'){}
	while(fileName[i] != '\0'){
		fileExtension[j] = fileName[i]; 
		j++;
		i++;
	}
	fileExtension[j] = '\0';
	if(strcmp(extension, fileExtension) == 0)
		return TRUE;
	else
		return FALSE;
}

string checkForConditionalGet(string str){
	int i = 0;
	string date = (string) malloc(15*sizeof(char));
	char word[50];

	while(str[i+2] != '\0'){
		i++;
		if(str[i] == 'I' && str[i+1] == 'f' && str[i+2] == '-'){
			printf("Conditional GET found!");
			str = SubString(str, i, strlen(str));
			sscanf(str, "%s %s", word, date);
			return date;
		}
	}
	return "No modify";
}

string SubString(string s, int p1, int p2){
    int len;
    string result;

    len = strlen(s);
    if (p1 < 0) p1 = 0;
    if (p2 >= len) p2 = len - 1;
    len = p2 - p1 + 1;
    if (len < 0) len = 0;
    result = (string) malloc(len);
    strncpy(result, s + p1, len);
    result[len] = '\0';
    return (result);
}


/*string checkForConditionalGet(string inputHTTP){
    char cmdHTTP[80];
	char filenameHTTP[80];
	char protocolHTTP[80];
	char name[80];
	char value[1000];
	char *space;
	int ok;
	char *next = strstr(inputHTTP,"\n");
	ok = sscanf(inputHTTP,"%s %s %s",cmdHTTP,filenameHTTP,protocolHTTP);
	while(next) {
		ok = sscanf(next+1,"%s",name);
		space = strstr(next+1," ");
		next = strstr(next+1,"\n");
		if(space) {
		    memcpy(value,space+1,next-space-1);
		    value[next-space-1]=0;
		}
		if(ok==1) {
			if(strcmp(name,"If-modified-since:")==0) {
				if(strcmp(cmdHTTP,"GET")==0) {
					return value;
				}
			}
		}
	}
	return "No modify";
}*/